###############################################################################
#
# file: PasswordDialog.py
# author: Victor Bos
# 
# Copyright 2007--2011 Victor Bos
#
# This file is part of GREATOR.
#
#    GREATOR is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    GREATOR is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with GREATOR.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
import Tkinter
import Tkconstants
import string
import hashlib
import unittest
import sys

# Valid passwords consists of the following characters.
PASSWORD_CHARS = string.digits + string.letters + string.punctuation

def hash_password(pw):
    """
    Creates an 'encrypted' password for the String pw.

    The encryption is an SHA message digest of pw.
    """
    if pw is None:
        return None
    return hashlib.sha1(pw).digest()


class Test_hash_password(unittest.TestCase):

    def test_hash_password(self):
        self.assertNotEqual(hash_password("hello"), hash_password("heIlo"))
        self.assertNotEqual(hash_password("hello"), hash_password("world"))
        s1 = "Hello world"
        s2 = "Hello world"
        self.assertEqual(hash_password(s1), hash_password(s2))


class PasswordEntry(Tkinter.Entry):

    def __init__(self, master=None, cnf={}, **kw):
        self.command = None
        self.minPwLength = 4
        Tkinter.Entry.__init__(self, master, kw)
        self.reset()
        self.bind("<BackSpace>", func=self.backspaceKey, add=False)
        self.bind("<KeyPress>", func=self.processKey, add=False)
        self.bind("<Return>", func=self.execCommand, add=False)
        self.bind("<Tab>", func=self.tabKey, add=False)

    def isValidChar(self, c):
        if len(c)>0:
            return c in PASSWORD_CHARS
        return False

    def hasMinPwLength(self, s):
        return len(s)==self.minPwLength

    def isValidPassword(self):
        return len(self.pw) >= self.minPwLength
    
    def processKey(self, e):
        c = e.char
        if self.isValidChar(c):
            self.pw += c
            self.insert(Tkconstants.END, "*")
        return "break"
    
    def backspaceKey(self, e):
        self.pw = self.pw[0:-1]
        self.delete(len(self.pw), Tkconstants.END)
        return "break"

    def tabKey(self, e):
        self.tk_focusNext()
        
    def execCommand(self, e=None):
        if self.command is not None:
            self.command(self.pw, self.isValidPassword())
        return "break"
        
    def reset(self):
        self.pw = ""
        self.delete(0, Tkconstants.END)
        
class PasswordEntryFrame(Tkinter.Frame):

    def __init__(self, master=None, cnf={}, **kw):
        o = Tkconstants.HORIZONTAL
        if "orientation" in kw:
            o = kw["orientation"]
            del kw["orientation"]
        packSide = Tkconstants.LEFT
        buttonExpand = False
        buttonFill = Tkconstants.NONE
        if o == Tkconstants.VERTICAL:
            packSide = Tkconstants.TOP,
            buttonExpand = True
            buttonFill = Tkconstants.X
            
        Tkinter.Frame.__init__(self, master, kw)
        self.e = PasswordEntry(self)
        self.e.pack(
            expand = True,
            fill = Tkconstants.X,
            side=packSide)
        self.b = Tkinter.Button(
            self,
            text = "Ok",
            command = self.e.execCommand)
        self.b.pack(
            expand = buttonExpand,
            fill = buttonFill,
            side = packSide)

    def setOption(self, key, default, kw):
        setattr(self, key, default)
        if kw.has_key(key):
            setattr(self, key, kw[key])
            del kw[key]
            

class PasswordBaseDialog(Tkinter.Toplevel):

    def __init__(self, master=None, **kw):
        self.process_options(kw)
        Tkinter.Toplevel.__init__(self, master, kw)
        self.protocol('WM_DELETE_WINDOW', self.cancel_pressed)
        self.title(self.windowtitle)

        self.message_label = Tkinter.Label(self, text = self.message)
        self.message_label.pack(side=Tkinter.TOP, anchor=Tkinter.W)

        f1 = self.create_main_frame()
        if f1 is not None:
            f1.pack(side = Tkinter.TOP, expand=1, fill=Tkinter.X)

        f2 = self.create_ok_cancel_buttons()
        f2.pack(side = Tkinter.TOP, anchor=Tkinter.E)

        self.pw = None
        self.center_on_master(master)

    def create_main_frame(self):
        return None
    
    def create_ok_cancel_buttons(self):
        f = Tkinter.Frame(self)
        
        self.ok_button = Tkinter.Button(
            f,
            text="Ok",
            command=self.ok_pressed)
        self.ok_button.pack(side=Tkinter.LEFT)

        self.cancel_button = Tkinter.Button(
            f,
            text="Cancel",
            command=self.cancel_pressed)
        self.cancel_button.pack(side=Tkinter.LEFT)
        return f
    
    def center_on_master(self, master):
        if master is not None:
            x = (master.winfo_rootx() +
                 ((master.winfo_width() - self.winfo_reqwidth()) / 2.0))
            y = (master.winfo_rooty() +
                 ((master.winfo_height() - self.winfo_reqheight()) / 2.0))
            self.geometry("+%d+%d" % (x,y))
        
    def process_options(self, kw):
        self.set_option("message", kw, "Please enter a password")
        self.set_option("windowtitle", kw, "Enter Password Dialog")
        
    def set_option(self, name, user_values, default):
        if name in user_values:
            setattr(self, name, user_values[name])
            del user_values[name]
        else:
            setattr(self, name, default)

    def ok_pressed(self):
        self.cancel_pressed()

    def cancel_pressed(self):
        self.pw = None
        self.quit()
        self.destroy()

    def go(self):
        self.wait_visibility() # window needs to be visible for the grab
        self.grab_set()
        self.mainloop()
        return self.pw

class PasswordDialog(PasswordBaseDialog):

    def create_main_frame(self):
        f1 = Tkinter.Frame(self)
        f1.pack(side = Tkinter.TOP, expand=1, fill=Tkinter.X)

        self.enter_label = Tkinter.Label(f1, text="Password")
        self.enter_label.pack(side=Tkinter.LEFT)
        
        self.pw_entry = PasswordEntry(f1)
        self.pw_entry.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=True)
        self.pw_entry.command = self.consolidate_password
        self.pw_entry.focus_set()
        return f1

    def ok_pressed(self):
        self.pw_entry.execCommand()
        
    def consolidate_password(self, pw, is_valid):
        if not is_valid:
            self.pw_entry.reset()
            self.pw = None
        else:
            self.pw = pw
        self.quit()
        self.destroy()
    

class ChangePasswordDialog(PasswordBaseDialog):

    def create_main_frame(self):
        f = Tkinter.Frame(self)
        f.pack(side = Tkinter.TOP, expand=1, fill=Tkinter.X)
        

        self.new_pw_label = Tkinter.Label(f, text="New password")
        self.new_pw_label.grid(
            row=0,
            column=0,
            sticky=Tkinter.EW)
        
        self.new_pw_entry = PasswordEntry(f)
        self.new_pw_entry.grid(
            row=0,
            column=1,
            sticky=Tkinter.EW)
        self.new_pw_entry.command = self.new_password

        # TAB should enter new password and then pass on focus to
        # self.confirm_pw_entry
        #self.new_pw_entry.bind("<Tab>", func=self.new_password, add=False)

        self.new_pw_entry.focus_set()

        self.confirm_pw_label = Tkinter.Label(f, text="Confirm password")
        self.confirm_pw_label.grid(
            row=1,
            column=0,
            sticky=Tkinter.EW)
        
        self.confirm_pw_entry = PasswordEntry(
            f,
            state=Tkinter.DISABLED)
        self.confirm_pw_entry.grid(
            row=1,
            column=1,
            sticky=Tkinter.EW)
        self.confirm_pw_entry.command = self.confirm_password
        f.columnconfigure(1, weight=1)
        return f

    def ok_pressed(self):
        self.confirm_pw_entry.execCommand()
        
    def new_password(self, pw, is_valid):
        if not is_valid:
            self.new_pw_entry.reset()
            self.confirm_pw_entry.reset()
            self.pw = None
        else:
            self.pw = pw
            self.confirm_pw_entry.configure(
                state=Tkinter.NORMAL)
            self.confirm_pw_entry.reset()
            self.confirm_pw_entry.focus_set()

    def confirm_password(self, pw, is_valid):
        if is_valid and (pw == self.pw):
            self.quit()
            self.destroy()
        else:
            self.new_pw_entry.reset()
            self.confirm_pw_entry.reset()
            self.new_pw_entry.focus_set()

def cmd(pw, valid):
    pass
#    enc_pw = hash_password(pw)
#    if valid:
#        print "PASSWORD:", pw, enc_pw.encode("hex_codec")
#    else:
#        print "INVALID PASSWORD:", pw, enc_pw.encode("hex_codec")
        
if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "test":
        del sys.argv[1]
        unittest.main()
        sys.exit()

    root = Tkinter.Tk()
    root.withdraw()
    pwd = PasswordDialog(
        windowtitle="Password Dialog",
        message="Enter X's password.")
    pw = pwd.go()
    if pw is not None:
        print "Password: '%s'" % pw
    else:
        print "No valid password"


    pwd = ChangePasswordDialog(
        windowtitle="Change Password Dialog",
        message="Enter new password twice")
    pw = pwd.go()
    if pw is not None:
        print "New password: '%s'" % pw
    else:
        print "No valid password"
        
    sys.exit()
    

    root.title("Password")
    c = Tkinter.Label(root, text="Type password")
    c.pack()
    pef = PasswordEntryFrame(
        root,
        orientation = Tkconstants.VERTICAL)
    pef.e.command = cmd
    pef.e.minPwLength = 5
    pef.pack(expand=True, fill=Tkconstants.BOTH)
    root.mainloop()
